package com.cybertron.am.wordpress.job.parser;

import com.cybertron.am.wordpress.domain.CrawlCategory;
import com.cybertron.am.wordpress.domain.CrawlProduct;
import com.cybertron.am.wordpress.job.CrawlResult;
import com.cybertron.am.wordpress.job.Parser;
import com.cybertron.am.wordpress.util.UrlUtil;
import com.jayway.jsonpath.PathNotFoundException;
import com.jayway.jsonpath.TypeRef;
import com.ne.boot.common.util.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.stereotype.Component;
import us.codecraft.xsoup.XElements;
import us.codecraft.xsoup.Xsoup;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

@Component
@Slf4j
public class KohlsParser implements Parser {

    @Override
    public String domain() {
        return "www.kohls.com";
    }

    @Override
    public List<CrawlCategory> getCategory(CrawlResult crawlResult) {
        String categoryHtml = getCategoryHtml();
        crawlResult.setHtml(categoryHtml);
        crawlResult.setDocument(Jsoup.parse(categoryHtml));
        XElements elements = Xsoup.select(crawlResult.getDocument(), "//li[@class='navigation-item']");
        List<CrawlCategory> categories = new ArrayList<>();
        for (Element firstEl : elements.getElements()) {
            String title = replaceTitle(Xsoup.select(firstEl, "/h2/a/html()").get());
            String url = Xsoup.select(firstEl, "/h2/a/@href").get();
            String firstId = StringUtils.isNotBlank(url) ? DigestUtils.md5Hex(url) : DigestUtils.md5Hex(crawlResult.getDomain() + ":" + title);
            categories.add(CrawlCategory.builder()
                    .id(firstId)
                    .title(title)
                    .build());
            Elements secondEls = Xsoup.select(firstEl, "//li[@class='nav-group']").getElements();
            for (Element secondEl : secondEls) {
                String secondUrl = UrlUtil.resolveUrl(crawlResult.getUrl(), Xsoup.select(secondEl, "/h3//a/@href").get());
                String secondId = DigestUtils.md5Hex(secondUrl);
                Elements thirdEls = Xsoup.select(secondEl, "/ul//li").getElements();
                for (Element thirdEl : thirdEls) {
                    String childUrl = UrlUtil.resolveUrl(crawlResult.getUrl(), Xsoup.select(thirdEl, "//a/@href").get());
                    categories.add(CrawlCategory.builder()
                            .id(DigestUtils.md5Hex(childUrl))
                            .title(replaceTitle(Xsoup.select(thirdEl, "//a/html()").get()))
                            .url(childUrl)
                            .parentId(secondId)
                            .build());
                }
                categories.add(CrawlCategory.builder()
                        .id(secondId)
                        .title(replaceTitle(Xsoup.select(secondEl, "/h3/a/html()").get()))
                        .url(CollectionUtils.isEmpty(thirdEls) ? secondUrl : null)
                        .parentId(firstId)
                        .build());
            }
        }
        return categories;
    }

    @Override
    public List<String> getProductUrl(CrawlResult crawlResult) {
        List<String> urls = Xsoup.select(crawlResult.getDocument(), "//*[@id=\"productsContainer\"]//li[@class=\"products_grid\"]//div[@class=\"prod_img_block\"]//a/@href").list();
        if (CollectionUtils.isEmpty(urls)) {
            return null;
        }
        return urls.stream()
                .map(v -> StringUtils.substringBeforeLast(v, "/") + "/product.jsp")
                .map(v -> UrlUtil.resolveUrl(crawlResult.getUrl(), v))
                .collect(Collectors.toList());
    }

    @Override
    public List<String> getNextPageUrl(CrawlResult crawlResult) {
        String nextUrl = StringUtils.substringBetween(crawlResult.getHtml(), "\"nextURL\":\"", "\"}");
        if (StringUtils.isBlank(nextUrl)) {
            return null;
        }
        List<NameValuePair> params = URLEncodedUtils.parse(nextUrl, Charset.defaultCharset());
        String ws = params.stream()
                .filter(v -> StringUtils.equals(v.getName(), "WS"))
                .map(NameValuePair::getValue)
                .findFirst()
                .orElse(null);
        if (ws == null) {
            return null;
        }
        if (Integer.parseInt(ws) > 3000) {
            return null;
        }
        return Collections.singletonList(UrlUtil.resolveUrl(crawlResult.getUrl(), nextUrl));
    }

    @Override
    public CrawlProduct getProduct(CrawlResult crawlResult) {
        String json = StringUtils.substringBetween(crawlResult.getHtml(), "var productV2JsonData = ", ";</script>");
        if (StringUtils.isBlank(json)) {
            return null;
        }
        String brand = readValue(json, "$.brand", new TypeRef<String>() {
        });
        String productTitle = readValue(json, "$.productTitle", new TypeRef<String>() {
        });
        productTitle = StringUtils.remove(productTitle, brand);
        productTitle = StringUtils.remove(productTitle, "®");
        productTitle = StringUtils.remove(productTitle, "™");
        productTitle = StringUtils.removeStart(productTitle, "'s ");
        productTitle = StringUtils.trim(productTitle);
        List<String> images = new ArrayList<>();
        List<String> heroImages = readValue(json, "$.images[*].url", new TypeRef<List<String>>() {
        });
        if (CollectionUtils.isNotEmpty(heroImages)) {
            List<String> urls = heroImages.stream()
                    .map(v -> StringUtils.substringBefore(v, "?") + "?wid=805&hei=805&op_sharpen=1")
                    .collect(Collectors.toList());
            images.addAll(urls);
        }
        List<String> options = new ArrayList<>();
        List<String> colorList = readValue(json, "$.variants.colorList", new TypeRef<List<String>>() {
        });
        List<String> sizeList = readValue(json, "$.variants.sizeList", new TypeRef<List<String>>() {
        });
        if (CollectionUtils.isNotEmpty(colorList)) {
            options.add("Color");
        }
        if (CollectionUtils.isNotEmpty(sizeList)) {
            options.add("Size");
        }
        String description = readValue(json, "$.description.longDescription", new TypeRef<String>() {
        });
        Document document = Jsoup.parse(description);
        Elements elements = Xsoup.select(document, "//a[@href*='kohls']").getElements();
        if (CollectionUtils.isNotEmpty(elements)) {
            for (Element element : elements) {
                element.remove();
            }
            description = Xsoup.select(document, "//body/html()").get();
        }
        List<Object> variantMaps = readValue(json, "$.SKUS", new TypeRef<List<Object>>() {
        });
        List<CrawlProduct.CrawlVariant> variants = CollectionUtils.isEmpty(variantMaps) ? null : variantMaps.stream()
                .map(o -> {
                    String variantJson = JsonUtil.writeValueAsString(o);
                    List<String> varOpts = new ArrayList<>();
                    String color = readValue(variantJson, "$.color", new TypeRef<String>() {
                    });
                    String size = readValue(variantJson, "$.size", new TypeRef<String>() {
                    });
                    if (StringUtils.isNotBlank(color)) {
                        varOpts.add(color);
                    }
                    if (StringUtils.isNotBlank(size)) {
                        varOpts.add(size);
                    }
                    String imgUrl = readValue(variantJson, "$.images[0].url", new TypeRef<String>() {
                    });
                    return CrawlProduct.CrawlVariant.builder()
                            .title(StringUtils.join(varOpts, "/"))
                            .price(readValue(variantJson, "$.price.salePrice.minPrice", new TypeRef<String>() {
                            }))
                            .image(StringUtils.isBlank(imgUrl) ? null : StringUtils.substringBefore(imgUrl, "?") + "?wid=805&hei=805&op_sharpen=1")
                            .options(varOpts.toArray(new String[]{}))
                            .build();
                })
                .collect(Collectors.toList());
        return CrawlProduct.builder()
                .id(DigestUtils.md5Hex(crawlResult.getUrl()))
                .url(crawlResult.getUrl())
                .title(productTitle)
                .brand(brand)
                .description(description)
                .price(readValue(json, "$.price.regularPrice.minPrice", new TypeRef<String>() {
                }))
                .images(CollectionUtils.isNotEmpty(images) ? images.toArray(new String[]{}) : null)
                .options(CollectionUtils.isNotEmpty(options) ? options.toArray(new String[]{}) : null)
                .variants(CollectionUtils.isNotEmpty(variants) ? variants.toArray(new CrawlProduct.CrawlVariant[]{}) : null)
                .build();
    }

    public String getCategoryHtml() {
        try {
            return IOUtils.toString(KohlsParser.class.getResourceAsStream("/kohls.html"), Charset.defaultCharset());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static <T> T readValue(String json, String jsonPath, TypeRef<T> typeRef) {
        try {
            return JsonUtil.readValue(json, jsonPath, typeRef);
        } catch (PathNotFoundException e) {
            log.debug(e.getMessage());
            return null;
        } catch (Exception e) {
            log.warn("读取JSON错误:" + e.getMessage());
            return null;
        }
    }

    public String replaceTitle(String title) {
        title = StringUtils.replace(title, "&amp;", "&");
        return title;
    }
}
